#include <linux/module.h>
#include <linux/fs.h>
#include <linux/usb/input.h>
#include <linux/hid.h>

/*
 * usb鼠标设备
 */
struct usb_mouse{
    char name[128];      /* 鼠标设备的名称，包括生产厂商、产品类别、产品等信息 */
    char phys[64];       /* 设备节点名称 */
    /* usb 鼠标是一种 usb设备，需要内嵌一个usb设备结构体来描述其usb属性 */
    struct usb_device *usbdev;
    /* usb鼠标同时又是一种输入设备，需要内嵌一个输入设备结构体来描述其输入设备的属性 */
    struct input_dev *dev;
    /** urb请求包结构体，用于传送数据 */
    struct urb *urb;

    /** 虚拟缓冲区地址 */
    char *data;
    /** DMA缓冲区 */
    dma_addr_t data_dma;
};

/**
 * urb完成时被调用的完成处理函数
 */
static void usb_mouse_irq(struct urb *urb)
{
     /* 私有数据  */
     struct usb_mouse *mouse = urb->context;
     /* 接收到的数据 */
     char *data = mouse->data;
     /* input_dev */
     struct input_dev *dev = mouse->dev;
     int status;

     /* urb的当前状态 */
     switch (urb->status) {
        case 0:                 /* success */
            break;
        case -ECONNRESET:       /* unlink */
        case -ENOENT:
        case -ESHUTDOWN:
            return;
        /* -EPIPE:  should clear the halt */
        default:                /* error */
            goto resubmit;
      }


    /*
     * usb鼠标数据含义
     * 向输入子系统汇报鼠标事件情况，以便作出反应。
     * data 数组的第0个字节：bit 0、1、2、3、4分别代表左、右、中、SIDE、EXTRA键的按下情况；
     * data 数组的第1个字节：表示鼠标的水平位移；
     * data 数组的第2个字节：表示鼠标的垂直位移；
     * data 数组的第3个字节：REL_WHEEL位移。
     */
     input_report_key(dev, BTN_LEFT,   data[0] & 0x01);
     input_report_key(dev, BTN_RIGHT,  data[0] & 0x02);
     input_report_key(dev, BTN_MIDDLE, data[0] & 0x04);
     input_report_key(dev, BTN_SIDE,   data[0] & 0x08);
     input_report_key(dev, BTN_EXTRA,  data[0] & 0x10);

     input_report_rel(dev, REL_X,     data[1]);
     input_report_rel(dev, REL_Y,     data[2]);
     input_report_rel(dev, REL_WHEEL, data[3]);

     /*上报同步事件,通知系统有事件上报 */
     input_sync(dev);

     /*
      * 系统需要周期性不断地获取鼠标的事件信息，因此在 urb 回调函数的末尾再次提交urb请求块，这样又会调用新的回调函数，周而复始。
      * 在回调函数中提交urb一定只能是 GFP_ATOMIC 优先级的，因为 urb 回调函数运行于中断上下文中，在提
      * 交 urb 过程中可能会需要申请内存、保持信号量，这些操作或许会导致 USB core 睡眠，一切导致睡眠的行
      * 为都是不允许的。
      */
resubmit:
        status = usb_submit_urb (urb, GFP_ATOMIC);
        if (status)
                dev_err(&mouse->usbdev->dev,
                        "can't resubmit intr, %s-%s/input0, status %d\n",
                        mouse->usbdev->bus->bus_name,
                        mouse->usbdev->devpath, status);
}

/**
 * 用户usb接口设备和usb接口驱动匹配
 */
static struct usb_device_id usb_mouse_id_table[] = {
        { USB_INTERFACE_INFO(
                     USB_INTERFACE_CLASS_HID,                 //接口类:hid类
                     USB_INTERFACE_SUBCLASS_BOOT,             //子类:启动设备类
                     USB_INTERFACE_PROTOCOL_MOUSE) },         //USB协议:鼠标协议
};


/*
 * 打开input_dev设备
 */
static int usb_mouse_open(struct input_dev *dev)
{
   /* 获取驱动数据 */
   struct usb_mouse *mouse = input_get_drvdata(dev);
   mouse->urb->dev = mouse->usbdev;

   /* 使用usb_submit_urb提交urb */
   if (usb_submit_urb(mouse->urb, GFP_KERNEL))
        return -EIO;

   return 0;
}

/* 关闭input_dev设备 */
static void usb_mouse_close(struct input_dev *dev)
{
    /* 获取驱动数据 */
    struct usb_mouse *mouse = input_get_drvdata(dev);

    /* 杀掉提交到内核中的urb */
    usb_kill_urb(mouse->urb);
}


/**
 * 当usb接口驱动和usb接口匹配成功之后，就会调用probe函数
 * 可以参考hub_probe实现
 */
static void usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
        /* 获取usb设备 */
        struct usb_device *dev = interface_to_usbdev(intf);
        /* 当前激活的接口配置 */
        struct usb_host_interface *interface;
        /* 当前usb接口下的端点0的端点描述符 */
        struct usb_endpoint_descriptor *endpoint;
        /* usb鼠标设备 */
        struct usb_mouse *mouse;
        /* input_dev */
        struct input_dev *input_dev;
        /* 端点管道 */
        int pipe,maxp;
        int error = -ENOMEM;

        /* 当前激活的接口配置 */
        interface = intf->cur_altsetting;

        /* 端点个数，鼠标只有1个 */
        if (interface->desc.bNumEndpoints != 1)
              return -ENODEV;

        /* 当前usb接口下的端点0的端点描述符 */
        endpoint = &interface->endpoint[0].desc;
        if (!usb_endpoint_is_int_in(endpoint))
              return -ENODEV;

        // 打印VID,PID
        printk("VID=%x,PID=%x\n",dev->descriptor.idVendor,dev->descriptor.idProduct);

        /* 通过usb_rcvintpipe创建一个端点管道 */
        pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
        /* 获取本端点接受或发送的最大信息包的大小 */
        maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));

        /* 动态分配内存 */
        mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL);

        /* 分配一个input_dev结构体  */
        input_dev = input_allocate_device();

        if (!mouse || !input_dev)
              goto fail1;

        /* 初始化 */
        mouse->usbdev = dev;
        mouse->dev = input_dev;

        /* USB控制器需要一块物理上连续的地址空间进行首发数据,因此使用DMA传输数据 */
        /**
         * 申请内存空间用于数据传输，data 为指向该空间的地址，data_dma 则是这块内存空间的 dma 映射，
         * 即这块内存空间对应的 dma 地址。在使用 dma 传输的情况下，则使用 data_dma 指向的 dma 区域，
         * 否则使用 data 指向的普通内存区域进行传输。
         * GFP_ATOMIC 表示不等待，GFP_KERNEL 是普通的优先级，可以睡眠等待，由于鼠标使用中断传输方式，
         * 不允许睡眠状态，data 又是周期性获取鼠标事件的存储区，因此使用 GFP_ATOMIC 优先级，如果不能
         * 分配到内存则立即返回 0。
         */
        mouse->data = usb_alloc_coherent(dev, 8, GFP_ATOMIC, &mouse->data_dma);
        if (!mouse->data)
              goto fail1;

        /* 分配一个urb数据结构体 */
        mouse->urb = usb_alloc_urb(0, GFP_KERNEL);
        if (!mouse->urb)
              goto fail2;

        /* 获取鼠标设备的名称 */
        if (dev->manufacturer)
              strlcpy(mouse->name, dev->manufacturer, sizeof(mouse->name));

        if (dev->product) {
               if(dev->manufacturer)
                      strlcat(mouse->name, " ", sizeof(mouse->name));
               strlcat(mouse->name, dev->product, sizeof(mouse->name));
        }

        /* 如果鼠标名没有 */
        if (!strlen(mouse->name))
                snprintf(mouse->name, sizeof(mouse->name),
                         "USB HIDBP Mouse %04x:%04x",
                         le16_to_cpu(dev->descriptor.idVendor),
                         le16_to_cpu(dev->descriptor.idProduct));

       /*
        * 填充鼠标设备结构体中的节点名。usb_make_path 用来获取 usb设备在 sysfs 中的路径，格式为：usb-usb 总线号-路径名。
        */
        usb_make_path(dev, mouse->phys, sizeof(mouse->phys));
        strlcat(mouse->phys, "/input0", sizeof(mouse->phys));

        /* 将鼠标设备的名称赋给鼠标设备内嵌的输入子系统结构体 */
        input_dev->name = mouse->name;
        /* 将鼠标设备的设备节点名赋给鼠标设备内嵌的输入子系统结构体 */
        input_dev->phys = mouse->phys;

       /*
        * input_dev 中的 input_id 结构体，用来存储厂商、设备类型和设备的编号，这个函数是将设备描述符
        * 中的编号赋给内嵌的输入子系统结构体
        */
        usb_to_input_id(dev, &input_dev->id);
        input_dev->dev.parent = &intf->dev;

        /* 设置上报事件，类型 */
        set_bit(EV_KEY, input_dev->evbit);    // 支持按键事件
        set_bit(EV_REL, input_dev->evbit);    // 支持相对坐标事件

        input_set_capability(input_dev, EV_KEY,BTN_LEFT);    // 鼠标左键点击   按下值为1，抬起值为0
        input_set_capability(input_dev, EV_KEY,BTN_RIGHT);   // 鼠标右键点击   按下值为1，抬起值为0
        input_set_capability(input_dev, EV_KEY,BTN_MIDDLE);  // 鼠标中键点击   按下值为1，抬起值为0
        input_set_capability(input_dev, EV_KEY,BTN_SIDE);
        input_set_capability(input_dev, EV_KEY,BTN_EXTRA);

        input_set_capability(input_dev, EV_REL,REL_X);
        input_set_capability(input_dev, EV_REL,REL_Y);
        input_set_capability(input_dev, EV_REL,REL_WHEEL);

        /* 设置input_dev->dev->driver_data = mouse*/
        input_set_drvdata(input_dev, mouse);

        /* 初始化input_dev */
        input_dev->open = usb_mouse_open;
        input_dev->close = usb_mouse_close;

        /* 填充urb */
        usb_fill_int_urb (mouse->urb ,                    //urb结构体
                          mouse->usbdev,                  //usb设备
                          pipe,                           //端点管道
                          mouse->data,                    //缓存区地址
                          (maxp > 8 ? 8 : maxp),          //数据长度
                          usb_mouse_irq,                  //中断函数
                          mouse,                          //urb完成函数上下文
                          endpoint->bInterval);           //中断间隔时间

         /* 支持DMA,所以要告诉urb结构体,已经使用了DMA缓冲区地址 */
         mouse->urb->transfer_dma = mouse->data_dma;                //设置DMA地址
         mouse->urb->transfer_flags  |= URB_NO_TRANSFER_DMA_MAP;    //设置使用DMA地址

        /* 注册input_dev */
        error = input_register_device(mouse->dev);
        if (error) {
           printk("input device usb mouse device registered failed\n");
           goto fail3;
        } else {
            printk("input device usb mouse device registered successfully\n");
        }

        /* 设置intf->dev->driver_data = mouse */
        usb_set_intfdata(intf, mouse);
        return 0;

fail3:
        /* 释放urb */
        usb_free_urb(mouse->urb);
fail2:
        /* 释放usb缓存区 */
        usb_free_coherent(dev, 8, mouse->data, mouse->data_dma);
fail1:
        /* 释放inpyt_dev */
        input_free_device(input_dev);
        kfree(mouse);
        return error;
}

/*
 * 卸载usb接口驱动时执行
 */
static usb_mouse_disconnect(struct usb_interface *intf)
{
    /* 获取intf->dev->driver_data */
     struct usb_mouse *mouse = usb_get_intfdata (intf);

     usb_set_intfdata(intf, NULL);

    if(mouse){
        /* 杀掉提交到内核中的urb */
        usb_kill_urb(mouse->urb);
        /* 注销内核中的input_dev */
        input_unregister_device(mouse->dev);
        /* 释放urb */
        usb_free_urb(mouse->urb);
        /* 释放usb缓存区 */
        usb_free_coherent(mouse->usbdev,8, mouse->data,mouse->data_dma);
        /* 释放input_dev */
        input_free_device(mouse->dev);
        kfree(mouse);
    }
}

/**
 * usb鼠标接口驱动
 */
static struct usb_driver usb_mouse_driver = {
        .name           = "usbmouse",
        .probe          = usb_mouse_probe,
        .disconnect     = usb_mouse_disconnect,
        .id_table       = usb_mouse_id_table,
};

/*
 *  usb鼠标接口驱动模块入口
 */
static int mouse_init(void)
{
   int ret;
   ret = usb_register(&usb_mouse_driver);
   if (ret){
       printk("usb interface driver registered failed\n");
   }else{
      printk("usb interface driver registered successfully\n");
   }
   return ret;
}

/*
 * usb鼠标接口驱动模块出口
 */
static void __exit mouse_exit(void)
{
    usb_deregister(&usb_mouse_driver);
    printk("usb interface driver deregistered successfully\n");
}

module_init(mouse_init);
module_exit(mouse_exit);
MODULE_LICENSE("GPL");